import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.category.*;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.general.*;
import org.jfree.data.xy.*;
import java.io.*;
import java.util.Map.Entry;
import java.util.*;
import java.lang.Class;
import java.lang.reflect.*;

/**
 * Graphics package for the Graphr language. Utilizes JFreeChart.
 * @author Michael T. Cole
 */
public class GraphrGraphics {
	/**
	 * Creates a graph from a GraphrAssociativeArray and saves it as a .jpg image file.
	 * @param type - the type of graph to be created (Consult LRM for appropriate types)
	 * @param title - the title of the graph
	 * @param firstLabel - the label for the x-axis
	 * @param secondLabel - the label for the y-axis
	 * @param data - associative array of data values to be graphed
	 * @param showLegend - a boolean indicating whether to show the legend
	 * @param filename - the name of file where the graph will be saved (Should be a JPEG) 
	 * @param width - the width of the graph
	 * @param height - the height of the graph
	 * @throws Exception - if type is wrong or arguments do not match.
	 * @return <b>true</b> if graph was created successfully; <b>false</b> otherwise.
	 */
	public static GraphrBoolean createGraph(GraphrString type, GraphrString title, GraphrString firstLabel, GraphrString secondLabel, GraphrAssociativeArray data, GraphrBoolean showLegend, GraphrString filename, GraphrNumber width, GraphrNumber height)throws Exception{
		//Local variables
		JFreeChart chart = null;
		Class c = null;
		DefaultCategoryDataset dataset = null;
		DefaultPieDataset piedata = null;
		
		//Adding data to data set
		if(type.toString().contains("PieChart")||type.toString().contains("RingChart")){
			//Special case if this is a Pie or Ring chart
			piedata = new DefaultPieDataset();
			for (Entry<String, GraphrDataType> entry : data.getHashMap().entrySet()) {
			      piedata.setValue(entry.getKey(), Double.parseDouble(entry.getValue().toString()));
			};
		}else{
			//Normal case
			dataset = new DefaultCategoryDataset();
			for (Entry<String, GraphrDataType> entry : data.getHashMap().entrySet()) {
			      dataset.setValue(Double.parseDouble(entry.getValue().toString()), secondLabel.getString(), entry.getKey());
			};
		}
		
		try{
			c = Class.forName("org.jfree.chart.ChartFactory");
		}catch(ClassNotFoundException e){}
		
		Method m[] = c.getDeclaredMethods();
		
		//Search for type in list of ChartFactory methods
		boolean notFound = true;
		int i = -1;
		while (notFound && i < m.length-1){
			i++;
			if(m[i].toString().contains(type.toString()))
				notFound = false;
		}
		
		//If found prepare arguments and invoke method
		if (i != -1 && i != m.length){
			Class[] arguments = m[i].getParameterTypes();
			Object[] args = new Object[arguments.length];
			
			//Special case for Pie and Ring charts
			if (m[i].toString().contains("PieChart") || m[i].toString().contains("RingChart") ){
				args[0] = title.toString();
				args[1] = piedata;
				args[2] = showLegend.getBoolean();
				args[3] = false;
				args[4] = new Locale("en");
			}else{
				//Normal case
				args[0] = title.toString();
				args[1] = firstLabel.toString();
				args[2] = secondLabel.toString();
				args[3] = dataset;
				
				if (arguments[4].toString().equals("boolean")){
					args[4] = showLegend.getBoolean();
				}
				else{
					args[4] = PlotOrientation.VERTICAL;
					args[5] = showLegend.getBoolean();
				}
				if (arguments.length > 6){
					args[6] = false;
					args[7] = false;
				}
			}
			try{
				//Invoke method
				chart = (JFreeChart)(m[i].invoke(null, args));
			}catch(InvocationTargetException e)
			{
				throw new Exception(type.toString() + " not a valid graph type.");
			}catch(IllegalAccessException e)
			{
				throw new Exception(type.toString() + " not a valid graph type.");
			}catch(IllegalArgumentException e)
			{
				throw new Exception(type.toString() + " not a valid graph type.");
			}
		}else{
			throw new Exception(type.toString() + " not found.");
		}
		
		try {
			//Save the chart as a jpeg
			ChartUtilities.saveChartAsJPEG(new File(filename.toString()), chart, width.toInt(),
	                height.toInt());
	   } catch (Exception e) {
		   return new GraphrBoolean(false);
	   }
	   
	   return new GraphrBoolean(true);
	}
	
	/**
	 * Creates a graph from a 2D GraphrArray and saves it as a .jpg image file.
	 * @param type - the type of graph to be created (Consult LRM for appropriate types)
	 * @param title - the title of the graph
	 * @param firstLabel - the label for the x-axis
	 * @param secondLabel - the label for the y-axis
	 * @param thirdLabel - the label for the legend
	 * @param data - associative array of data values to be graphed
	 * @param showLegend - a boolean indicating whether to show the legend
	 * @param filename - the name of file where the graph will be saved (Should be a JPEG) 
	 * @param width - the width of the graph
	 * @param height - the height of the graph
	 * @throws Exception - if type is wrong or arguments do not match.
	 * @return <b>true</b> if graph was created successfully; <b>false</b> otherwise.
	 */
	public static GraphrBoolean createGraph(GraphrString type, GraphrString title, GraphrString firstLabel, GraphrString secondLabel, GraphrString thirdLabel, GraphrArray data, GraphrBoolean showLegend, GraphrString filename, GraphrNumber width, GraphrNumber height)throws Exception{
		JFreeChart chart = null;
		Class c = null;
		XYSeries series = new XYSeries(thirdLabel.toString());
		
		//Add data to data set
		ArrayList<GraphrDataType> outer = data.getArray();
		for(int i = 0; i < outer.size(); i++){
			GraphrArray temp= (GraphrArray)outer.get(i);
			ArrayList<GraphrDataType> inner = temp.getArray();
			try{
			series.add(Double.parseDouble(inner.get(0).toString()), Double.parseDouble(inner.get(1).toString()));
			}catch(Exception e){
				throw new Exception ("Data is not in the correct format.");
			}
		}
		
		XYSeriesCollection dataset = new XYSeriesCollection();
        dataset.addSeries(series);

		try{
			c = Class.forName("org.jfree.chart.ChartFactory");
		}catch(ClassNotFoundException e){}
		
		Method m[] = c.getDeclaredMethods();
		
		//Search for type in list of ChartFactory methods
		boolean notFound = true;
		int i = -1;
		while (notFound && i < m.length-1){
			i++;
			if(m[i].toString().contains(type.toString()))
				notFound = false;
		}
		
		//If found prepare arguments and invoke method
		if (i != -1 && i != m.length){
			Class[] arguments = m[i].getParameterTypes();
			Object[] args = new Object[arguments.length];
			
			try{
				args[0] = title.toString();
				args[1] = firstLabel.toString();
				args[2] = secondLabel.toString();
				args[3] = dataset;
				
				//Special case for time series graphs
				if (m[i].toString().contains("TimeSeries")){
					args[4] = showLegend.getBoolean();
					args[5] = false;
					args[6] = false;
				}
				else{
					args[4] = PlotOrientation.VERTICAL;
					args[5] = showLegend.getBoolean();
					args[6] = false;
					args[7] = false;
				}
			}catch(ArrayIndexOutOfBoundsException e){
				throw new Exception ("Data is not in the correct format.");
			}
			
			try{
				//Invoke the method
				chart = (JFreeChart)(m[i].invoke(null, args));
			}catch(InvocationTargetException e)
			{
				throw new Exception(type.toString() + " not a valid graph type.");
			}catch(IllegalAccessException e)
			{
				throw new Exception(type.toString() + " not a valid graph type.");
			}catch(IllegalArgumentException e)
			{
				throw new Exception(type.toString() + " not a valid graph type.");
			}
		}else{
			throw new Exception(type.toString() + " not found.");
		}
		
		try {
			//Save the graph as jpeg
			ChartUtilities.saveChartAsJPEG(new File(filename.toString()), chart, width.toInt(),
	                height.toInt());
	   } catch (Exception e) {
		   return new GraphrBoolean(false);
	   }
	   
	   return new GraphrBoolean(true);
	}
}
